package org.moyrax.nereyda.services.core
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.net.FileReference;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	
	import mx.controls.Alert;
	import mx.managers.CursorManager;
	
	import org.moyrax.nereyda.lib.HashCollection;
	import org.moyrax.nereyda.services.errors.ServiceError;
	import org.moyrax.nereyda.services.events.*;
	import org.moyrax.nereyda.services.factory.*;
	import org.moyrax.nereyda.services.template.*;
	
	public class Sender
	{
		public static var  configure : Configure = null;
		private static var urlLoader : URLLoader = new URLLoader();
		private static var factory : Array = [];
		
		private static var currentFile  :FileReference;
		private static var progressProc :Function;
		
		private static var uploadQueue :HashCollection = new HashCollection();

		/**
		 * Ejecuta la petición especificada en el servidor.
		 * Las respuestas llegarán a los eventos definidos
		 * en la clase Configure vinculada a este objeto.
		 * 
		 * @param request Petición que se enviará.
		 */
		public static function invoke( request:Request ):void
		{
			invokeAndUpload( request, null, null );
		}

		public static function invokeAndUpload( request:Request, file:FileReference = null, progressProc:Function = null ):void
		{
			if ( configure == null )
				return;
			
			var urlRequest :URLRequest = new URLRequest( configure.remoteHost );
			var urlVars    :URLVariables = new URLVariables();
			
			// Verifica si la petición posee parámetros.
			//
			if ( request.params == null ) {
				request.params = new Parameter();
			}
			
			request.params.addParam( "service", request.service );

			urlLoader.dataFormat = configure.contentType;
			
			urlVars.decode( makeRequestString( request ) );
			
			urlRequest.contentType = configure.contentType;
			urlRequest.method = URLRequestMethod.POST;
			urlRequest.data = urlVars;
			
			// Agrega los eventos predeterminados.
			//
			addEventListeners( file, progressProc );

			CursorManager.setBusyCursor();
			
			if ( file ) {
				uploadQueue.put( file, request );
				
				file.upload( urlRequest, "file" );
			}
			else {
				urlLoader.load( urlRequest );
			}
		}
		
		/**
		 * Agrega un nuevo controlador de contenido que
		 * se encargará de crear y resolver las respuestas
		 * que devuelvan los servicios.
		 * 
		 * @param contentFactory Controlador de contenido que se usará.
		 * @param contentType    Tipo de contenido que se controlará con esta factory.
		 */
		public static function addContentHandler( contentFactory:IContentFactory, contentType:String ):void
		{
			if ( !contentType )
				return;

			factory[contentType] = contentFactory;
		}

		/**
		 * Devuelve el controlador de contenidos asociado
		 * al tipo mime especifiado.
		 */
		public static function getContentHandler( contentType:String = null ):IContentFactory
		{
			if ( !contentType && !configure.contentType )
				return(null);

			return( factory[contentType || configure.contentType ] );
		}
		
		/**
		 * Se desencadena cuando la llamada al servicio se
		 * completó satisfactoriamente.
		 */
		private static function onResultHandler( event:Object ):void
		{
			if ( event.target is FileReference )
			{
/**				// Finalizó la carga de un archivo en el servidor.
				//
				var request :Request = uploadQueue.get( event.target ) as Request;
				
				request.params.addParam( "file", {
					"creationDate" : event.target.creationDate,
					"modificationDate" : event.target.modificationDate,
					"name" : event.target.name,
					"size" : event.target.size,
					"type" : event.target.type
				});
				
				invoke( request );
				*/
				return;
			}

			CursorManager.removeBusyCursor();
			
			var servObj   :IServiceObject = IServiceObject( 
				getContentHandler().parseResponse( Event(event) )
			);
			var respEvent :IServiceEvent  = null;

			if ( servObj == null ) 
			{
				// La factory no pudo reconstruir el mensaje debido
				// a un error del servidor. Se delega la operación.

				onFaultHandler( event );
				
				return;
			}

			// Verifica si el ServiceObject tiene un evento asociado.
			//
			if ( getContentHandler().mappings.objectHasEvent(servObj) ) {
				respEvent = factory[ configure.contentType ].createEvent( servObj );
			}
			else {
				// Si no tiene un evento asociado, desencadena el
				// evento preestablecido en la configuración.
				//
				respEvent = new RequestSuccessEvent(servObj);


				if ( configure.onResultEvent != null ) {
					configure.onResultEvent( respEvent );
				}
			}

			respEvent.rawData = event.target.data;
			
			ServiceEventDispatcher.getInstance().dispatchEvent( Event(respEvent) );
			
			removeEventListeners();
		}

		/**
		 * Se desencadena cuando la llamada al servicio falló.
		 */
		private static function onFaultHandler( event:Object ):void
		{
			var response   :ServiceError = factory[ configure.contentType ].parseError(event);
			var faultEvent :RequestFailEvent;

			// Verifica si es un error controlado, o un error crítico del servidor.
			//
			if ( response.code == ServiceError.UNKNOWN_ERROR ) {
				faultEvent = new UnhandledErrorEvent( null );
			}
			else {
				faultEvent = new RequestFailEvent( null );
			}
			
			faultEvent.error = response;
			faultEvent.rawData = event.target.data;
			
			// Desencadena los eventos correspondientes
			// definidos en la configuración.
			//
			if ( (configure.onFaultEvent != null) && (response.code != ServiceError.UNKNOWN_ERROR ) ) 
			{
				configure.onFaultEvent( faultEvent );
			}
			else if ( configure.unhandledErrorEvent != null ) 
			{
				configure.unhandledErrorEvent( faultEvent );
			}
			
			ServiceEventDispatcher.getInstance().dispatchEvent(faultEvent);

			removeEventListeners();
		}
		
		/**
		 * Crea una cadena con el formato apropiado para
		 * enviar la petición al servidor.
		 * 
		 * @param request Petición que se transformará en String.
		 */
		private static function makeRequestString( request:Request ):String
		{
			var content :IContentFactory = factory[ configure.contentType ];
			
			content.encoder = configure.encoder;
			
			return( content.makeRequestStr(request) );
		}
		
		private static function addEventListeners( file:FileReference = null, progressCallback:Function = null ):void 
		{
			urlLoader.addEventListener( Event.COMPLETE, onResultHandler );
			urlLoader.addEventListener( IOErrorEvent.IO_ERROR, onFaultHandler );
			
			ServiceEventDispatcher.getInstance().addEventListener(
				ServiceEvent.REQUEST_SUCCESS,
				configure.onResultEvent
			);
			ServiceEventDispatcher.getInstance().addEventListener(
				ServiceEvent.REQUEST_FAIL,
				configure.onResultEvent
			);
			
			if ( file ) {
				file.addEventListener( Event.COMPLETE, onResultHandler );
				file.addEventListener( IOErrorEvent.IO_ERROR, onFaultHandler );
				file.addEventListener( ProgressEvent.PROGRESS, progressCallback );
			}
			
			currentFile = file;
			progressProc = progressCallback; 
		}
		
		private static function removeEventListeners():void 
		{
			urlLoader.removeEventListener( Event.COMPLETE, onResultHandler );
			urlLoader.removeEventListener( IOErrorEvent.IO_ERROR, onFaultHandler );
			ServiceEventDispatcher.getInstance().removeEventListener(
				ServiceEvent.REQUEST_SUCCESS,
				configure.onResultEvent
			);
			ServiceEventDispatcher.getInstance().removeEventListener(
				ServiceEvent.REQUEST_FAIL,
				configure.onResultEvent
			);
			if ( currentFile ) {
				currentFile.removeEventListener( Event.COMPLETE, onResultHandler );
				currentFile.removeEventListener( IOErrorEvent.IO_ERROR, onFaultHandler );
				currentFile.removeEventListener( ProgressEvent.PROGRESS, progressProc );
			}
		}
	}
}